#include "mexContainer.h"

// Define mxContainer class
//
#ifdef DEBUG
#define mexDebugPrintValue(str, arg) { printf("%s mxValue = %p\n", str, arg); }
#define mexDestroyArray( arg ) 
//mexDebugPrintValue("Destroying array: ", arg); mxDestroyArray(arg);
#else
#define mexDebugPrintValue(str, arg)
#define mexDestroyArray( arg ) mxDestroyArray(arg);
#endif

mxContainer::mxContainer() : mxValue(NULL) {};

mxContainer::mxContainer(mxArray * value) : mxValue(value) {};

mxContainer::mxContainer(const mxContainer & other) {
	mxValue = mxDuplicateArray(other.mxValue);
}

mxContainer::mxContainer(const double & dval)  {
    mxValue = mxCreateDoubleScalar(dval);
}

mxContainer::mxContainer(const string & str)  {
    mxValue = mxCreateString(str.c_str());
}

mxContainer::mxContainer(const char * str)  {
    mxValue = mxCreateString(str);
}

mxContainer::mxContainer(const char * stringList[])  {
    int count;
    for(count = 0; stringList[count] != NULL; count++){
        rassert( count < 3000 ); // stringList must be NULL terminated!!
    }
    mwSize dims[1] = { count };
    mxValue = mxCreateCellArray( 1, dims );
    for(count = 0; stringList[count] != NULL; count++){
        mxArray * str = mxCreateString( stringList[count] );
        mxSetCell( mxValue, count, str );
    }
}

mxContainer::mxContainer(const parameter & param)  {
    mwSize mSize = param.GetSize();
    if( mSize == 1 ){
        switch( param.getType() ){
          case parameter::Integer:
	    (*this) = static_cast<double>(param.getiValue());
            break;
          case parameter::Real:
	    (*this) = param.getfValue();
            break;
          case parameter::String:
	    (*this) = param.getsValue();
            break;
          default:
            fatal_stop("Cannot handle this profile type."); 
        };
    }else{
        const parameter & prof = param;
        mxClassID classid = mxDOUBLE_CLASS;
        mwSize mSize = static_cast<mwSize>( prof.GetSize() );
        mwSize dims[1] = { mSize };
        switch(prof.getType()){ 
          case parameter::Integer: 
          case parameter::Real: 
            { 
                mxValue = mxCreateNumericArray( 1, dims, classid, mxREAL );
                rassert( mxValue != NULL ); 
                double * pRawData = static_cast<double*>(mxGetData( mxValue )); 
                if( prof.getType() == parameter::Integer ){ 
                    const vector<int> & data = prof.getiData(); 
                    for(size_t i = 0; i < data.size(); i++) pRawData[i] = data[i];   
                }else{ 
                    const vector<REAL> & data = prof.getData(); 
                    for(size_t i = 0; i < data.size(); i++) pRawData[i] = data[i];   
                } 
            } 
            break; 
          case parameter::String: 
            { 
                mxValue = mxCreateCellArray(mSize, dims); 
                const vector<string> & data = prof.getsData(); 
                for(size_t i = 0; i < data.size(); i++){ 
                    mxSetCell(mxValue, i, mxCreateString(data[i].c_str())); 
                } 
            } 
            break; 
          default: 
            fatal_stop("Cannot handle this profile type."); 
        } 
    }
}

    template <class T, int S>
mxContainer::mxContainer( const rank<T, S> * indexmap, const int size ) 
{
    mwSize ndims = 2;
    mwSize dims[2] = { size, S };
    mxValue = mxCreateNumericArray( ndims, dims, mxINT16_CLASS, mxREAL );
    short * mxIndexListData = static_cast<short *>(mxGetData( mxValue ));
    for(int row=0; row < size; row++){
        const rank<INDEX, S> & indexValue = indexmap[row];
        for(int col=0; col < S; col++){
            mxIndexListData[ col*size+row ] = indexValue[col];
        }
    }   
}

    template<class T, int S> 
mxContainer::mxContainer(const vector< rank<T, S> > & input) 
{
    const size_t size = input.size();
    mwSize ndims = 2;
    mwSize dims[2] = { size, S };
    mxValue = mxCreateNumericArray( ndims, dims, mxINT16_CLASS, mxREAL );
    short * mxIndexListData = static_cast<short *>(mxGetData( mxValue ));
    for(int row=0; row < size; row++){
        const rank<INDEX, S> & indexValue = input[row];
        for(int col=0; col < S; col++){
            mxIndexListData[ col*size+row ] = indexValue[col];
        }
    }   
}

template mxContainer::mxContainer(const vector< rank<INDEX, 3> > & );

template<class T, int S>
bool mxContainer::unpack( rank<T,S> * & indexMap ) const {
    const mwSize ndims = mxGetNumberOfDimensions( mxValue );
    if( mxGetClassID( mxValue ) == mxINT16_CLASS && ! mxIsComplex( mxValue ) && ndims == 2 ){
        const mwSize * dims = mxGetDimensions(mxValue);
        const mwSize size = dims[0];
        indexMap = new rank<T,S> [ size ];
        const short * mxIndexListData = static_cast<const short *>(mxGetData( mxValue ));
        for(int row=0; row < size; row++){
            rank<INDEX, S> & indexValue = indexMap[row];
            for(int col=0; col < S; col++){
                indexValue[col] = mxIndexListData[ col*size+row ];
            }
        }   
    }
    return false;
}

mxContainer::~mxContainer() {
    if( mxValue != NULL ){
        mexDestroyArray( mxValue );
        mxValue = NULL;
    }
}

bool mxContainer::isEmpty() const {
    return mxValue == NULL;
}

static bool mxIsObjectEqual( const mxArray *, const mxArray *, const int);

static bool mxIsStructObjectEqual(const mxArray * c1, const mxArray * c2, const int numElements){
    int numFields = mxGetNumberOfFields(c1);
    if( mxGetNumberOfFields(c2) != numFields ) return false;
    for(int arrayIter = 0; arrayIter < numElements; arrayIter ++){
        for(int fieldIter = 0; fieldIter < numFields; fieldIter++){
            const mxArray * mxField1 = mxGetFieldByNumber(c1, arrayIter, fieldIter);
            const mxArray * mxField2 = mxGetFieldByNumber(c2, arrayIter, fieldIter);
            bool areEqual = mxIsObjectEqual( mxField1, mxField2, numElements );
            if( !areEqual ) return false;
        }
    }
    return true;
}

static bool mxIsCellObjectEqual(const mxArray * c1, const mxArray * c2, const int numElements){
    for(int arrayIter = 0; arrayIter < numElements; arrayIter ++){
        mxArray * cell1 = mxGetCell(c1, arrayIter);
        mxArray * cell2 = mxGetCell(c2, arrayIter);
        bool areEqual = mxIsObjectEqual( cell1, cell2, numElements );
        mexDestroyArray( cell1 );
        mexDestroyArray( cell2 );
        if( !areEqual ) return false;
    }
    return true;
}


template <class T>
static bool mxIsRealValuedObjectEqual(const mxArray * c1, const mxArray * c2, const int numElements ){
    bool isCmplx = mxIsComplex( c1 );
    for(int i = 0; i < numElements; i++){
        T * data1 = static_cast<T*>(mxGetData( c1 ));
        T * data2 = static_cast<T*>(mxGetData( c2 ));
        if( ( *data1 ) != ( *data2 ) ) return false;
        if( isCmplx ){
            T * data1 = static_cast<T*>(mxGetImagData( c1 ));
            T * data2 = static_cast<T*>(mxGetImagData( c2 ));
            if( ( *data1 ) != ( *data2 ) ) return false;
        }	
    }
    return true;
}

static bool mxIsObjectEqual( const mxArray * c1, const mxArray * c2, const int numElements) {
    switch( mxGetClassID( c1 ) ){
      case mxCELL_CLASS: 
        return mxIsCellObjectEqual(c1, c2, numElements);
        break;
      case mxSTRUCT_CLASS:
        return mxIsStructObjectEqual(c1, c2, numElements);
        break;
      case mxLOGICAL_CLASS: 
        return mxIsRealValuedObjectEqual<bool>( c1, c2, numElements );
        break;
      case mxCHAR_CLASS:
        return mxIsRealValuedObjectEqual<char>( c1, c2, numElements );
        break;
      case mxDOUBLE_CLASS:
        return mxIsRealValuedObjectEqual<double>( c1, c2, numElements );
        break; 
      case mxINT8_CLASS: 
        return mxIsRealValuedObjectEqual<char>( c1, c2, numElements );
        break;
      case mxUINT8_CLASS: 
        return mxIsRealValuedObjectEqual<unsigned char>( c1, c2, numElements );
        break;	
      case mxSINGLE_CLASS:
      case mxINT16_CLASS: 
      case mxUINT16_CLASS: 
      case mxINT32_CLASS:
      case mxUINT32_CLASS: 
      case mxINT64_CLASS: 
      case mxUINT64_CLASS: 
      default:
        fprintf(stderr, "Cannot handle mxObject type.");
        rassert(false);
    };	
}


// Compares mxArrays (does NOT do matrix comparisions!)
bool mxContainer::operator==(const mxContainer & other) const
{
    if( mxValue == NULL && other.mxValue == NULL ) return true;
    if( mxValue == NULL || other.mxValue == NULL ) return false;
    // check class id
    if( mxGetClassID( mxValue ) != mxGetClassID( other.mxValue ) ) return false;
    // Check shape and structure
    int numDims = mxGetNumberOfDimensions( mxValue );
    if( numDims != mxGetNumberOfDimensions( other.mxValue ) ) return false;
    const mwSize * dims1 = mxGetDimensions( mxValue );
    const mwSize * dims2 = mxGetDimensions( other.mxValue );
    for( int i = 0; i < numDims; i++) {
        if( dims1[i] != dims2[i] ) return false;
    }
    int numElements = mxGetNumberOfElements( mxValue );
    if( numElements != mxGetNumberOfElements( other.mxValue ) ) return false;
    // we only handle some basic comparisons here.
    return mxIsObjectEqual( mxValue, other.mxValue, numElements );
}

bool mxContainer::operator==(const double other) const
{
    if( mxValue == NULL ) return false;
    if( ! mxIsDouble( mxValue ) ) return false;
    if(  mxIsComplex( mxValue ) ) return false;
    return mxGetScalar( mxValue ) == other ;
}

bool mxContainer::operator==(const char * other) const
{
    if( mxValue == NULL ) return false;
    if( ! mxIsChar( mxValue ) ) return false;
    char * mxValueAsString = mxArrayToString( mxValue );
    bool rval = strcmp( mxValueAsString, other) == 0;
    mxFree( mxValueAsString );
    return rval;	
}

bool mxContainer::operator==(const parameter& param) const
{
    switch( param.getType() ){
      case parameter::Integer:
        return (*this) == param.getiValue();
      case parameter::Real:
        return (*this) == param.getfValue();
      case parameter::String:
        return (*this) == param.getsValue();
      default:
        fatal_stop("Cannot handle this parameter/profile type."); 
    };

}

mxContainer & mxContainer::operator=(const mxContainer & other)
{
       this->mxValue = mxDuplicateArray( other.mxValue );
}

const mxArray * mxContainer::mxGetArray() const {
    return mxValue;
}

bool mxContainer::unpack( double & val ) const {
    if( mxIsDouble( mxValue ) && ~mxIsComplex(mxValue) && mxGetNumberOfElements( mxValue ) == 1 ){
        val = mxGetScalar(mxValue); 
        return true;
    }
    return false;
}

bool mxContainer::unpack( string & val ) const {
    if( mxIsChar( mxValue ) ){
        size_t size = static_cast<size_t>( mxGetNumberOfElements(mxValue) );
        val.resize( size, 0 );
        mxChar * sData = mxGetChars( mxValue );
        for( size_t i = 0; i < size; i++) val[i] = static_cast<char>( sData[i] );
        return true;
    }
    return false;
}
// ========================================================================== //
// Class mxVector (std::vector like interface for mxContainer 		      //
// ========================================================================== //
template<>
mxVector<double>::mxVector( size_t s, const double & initializer ){
	if( s == 0 ) return;
	mwSize dims[1] = { s };
	mxValue = mxCreateNumericArray( 1, dims, mxDOUBLE_CLASS, mxREAL );
	double * rawData = mxGetPr(mxValue);
	for(size_t i = 0; i < s; i ++) rawData[i] = initializer;
};

template<>
double & mxVector<double>::operator[](size_t index) {
	rassert( mxValue != NULL );
	size_t s = mxGetNumberOfElements( mxValue );
	rassert( index < s );
	rassert( mxGetNumberOfDimensions(mxValue) == 2 );
	double * rawData = mxGetPr(mxValue);
	return rawData[index];
}

template<>
size_t mxVector<double>::size() const {
	if( mxValue == NULL ) return 0;
	size_t s = mxGetNumberOfElements( mxValue );
	rassert( mxGetNumberOfDimensions(mxValue) == 2 );
        return s;	
}

// ========================================================================== //
// Class mxVector (std::vector like interface for mxContainer 		      //
// ========================================================================== //
mxComplexRef::mxComplexRef( complex<double> & init ) :
	rPtr(&(init.real())),
	iPtr(&(init.imag())) 
{
	rassert( rPtr && iPtr );
}

mxComplexRef::mxComplexRef(double * real, double * imag) :
	rPtr(real),
	iPtr(imag)
{
	rassert( rPtr && iPtr );
}

mxComplexRef & mxComplexRef::operator=( const qgcomplex & val)
{
	*rPtr = val.real();
	*iPtr = val.imag();
}

double & mxComplexRef::real(){
	return *rPtr;
}

const double & mxComplexRef::real() const
{
	return *rPtr;
}

double & mxComplexRef::imag() 
{
	return *iPtr;
}
const double & mxComplexRef::imag() const
{
	return *iPtr;
}

mxComplexRef::~mxComplexRef() {};

// ========================================================================== //
// Class mxVector (std::vector like interface for mxContainer 		      //
// ========================================================================== //
mxVector<mxComplexRef>::mxVector( size_t s, const mxComplexRef & initializer ){
	if( s == 0 ) return;
	mwSize dims[1] = { s };
	mxValue = mxCreateNumericArray( 1, dims, mxDOUBLE_CLASS, mxCOMPLEX );
	double * realRawData = mxGetPr(mxValue);
	double * imagRawData = mxGetPi(mxValue);
	for(size_t i = 0; i < s; i ++) realRawData[i] = initializer.real();
	for(size_t i = 0; i < s; i ++) imagRawData[i] = initializer.imag();
};

mxComplexRef & mxVector<mxComplexRef>::operator[](size_t index) {
	rassert( mxValue != NULL );
	size_t s = mxGetNumberOfElements( mxValue );
	rassert( index < s );
	rassert( mxGetNumberOfDimensions(mxValue) == 2 );
	double * realPtr = mxGetPr(mxValue) + index;
	double * imagPtr = mxGetPi(mxValue) + index;
	if( refMap.find(index) != refMap.end() ){
		mxComplexRef & ref = *(refMap[index]);
		rassert( realPtr == &( ref.real() ) );
		rassert( imagPtr == &( ref.imag() ) );
		return ref;
	}else{
		mxComplexRef * refPtr = new mxComplexRef( realPtr, imagPtr );
		refMap[index] = refPtr;
		return *refPtr;
	}
}

size_t mxVector<mxComplexRef>::size() const {
	if( mxValue == NULL ) return 0;
	size_t s = mxGetNumberOfElements( mxValue );
	rassert( mxGetNumberOfDimensions(mxValue) == 1 );
	return s;	
}

// ========================================================================== //
// Class mxMatrix (std::vector like interface for mxContainer) 		      //
// ========================================================================== //

template<>
mxMatrix<uint8_T>::mxMatrix( size_t mSize, size_t nSize, const uint8_T & initializer ){
	if( mSize == 0 || nSize == 0 ) return;
	mwSize mxDims[2] = { mSize, nSize };
	dims[0] = mSize;
	dims[1] = nSize;
	mxValue = mxCreateNumericArray( 2, mxDims, mxUINT8_CLASS, mxREAL );
	uint8_T * rRawData = static_cast<uint8_T*>(mxGetData( mxValue ));
	for(size_t i = 0; i < mSize * nSize; i ++){
		rRawData[i] = initializer;
	}
};

template <>
uint8_T & mxMatrix<uint8_T>::operator()(size_t i, size_t j){
	rassert( mxValue != NULL );
	size_t index = j* static_cast<size_t>( dims[0] ) + i;
	rassert( index < mxGetNumberOfElements(mxValue) );
	uint8_T * rRawData = static_cast<uint8_T*>(mxGetData( mxValue ));
	return rRawData[ index ];
}

template <>
const uint8_T & mxMatrix<uint8_T>::operator()(size_t i, size_t j) const 
{
	rassert( mxValue != NULL );
	size_t index = j* static_cast<size_t>( dims[0] ) + i;
	rassert( index < mxGetNumberOfElements(mxValue) );
	uint8_T * rRawData = static_cast<uint8_T*>(mxGetData( mxValue ));
	return rRawData[ index ];
}

template <>
size_t mxMatrix<uint8_T>::mSize() const {
	return static_cast<size_t>( dims[0] );
}

template <>
size_t mxMatrix<uint8_T>::nSize() const {
	return static_cast<size_t>( dims[1] );
}



// ========================================================================== //
// Class mxStruct 							      //
// ========================================================================== //
static mxArray * mxCreateEmptyStruct() {
    mwSize msize = 1;
    mwSize dims[1] = { msize };
    mxArray * structBlock = mxCreateStructArray(msize, dims, 0, NULL);
    return structBlock;
}

mxStruct::mxStruct() : mxContainer() {
    mxValue = mxCreateEmptyStruct();
} 

mxStruct::mxStruct(const mxStruct & other) : mxContainer() {
    static_cast< mxContainer & >(*this) = static_cast<const mxContainer & >(other);
}

mxStruct::mxStruct( mxArray * mxstrct ) {
    rassert( mxIsStruct( mxstrct ) );
    mxValue = mxstrct;
}

template <class T, int S>
mxStruct::mxStruct( const map_container<T,S> & map, const char * description ){
    mxValue = mxCreateEmptyStruct();
    const T * data = map.getcoefficients();
    const rank<INDEX, S> * indexmap = map.get_index_map();
    const int size = map.get_size();
    if( size == 0 ) return;

    mxContainer mxDesc( description );
    setField( "Description", mxDesc );

    mxContainer mxIndexList( indexmap, size );
    setField( "Index", mxIndexList );

    // Mashall in data values
    {
      mxArray * mxFieldData = mxMarshalInData( size, data );
      mexDebugPrintValue( "Assigning ptr to field:", mxFieldData );
      int fieldNumber = mxAddField( mxValue, "Data" );
      mxSetFieldByNumber( mxValue, 0, fieldNumber, mxFieldData );
    }
}

template mxStruct::mxStruct( const map_container<qgcomplex, 11> &, const char *);
template mxStruct::mxStruct( const map_container<REAL, 6> &, const char *);
template mxStruct::mxStruct( const map_container<REAL, 3> &, const char *);

// ..................................................................................................................... 

mxArray * mxMarshalInData( const int size, const qgcomplex * data ){
    mwSize ndims = 1;
    mwSize dims[1] = { size };
    mxArray * mxFieldData = mxCreateNumericArray( ndims, dims, mxDOUBLE_CLASS, mxCOMPLEX );
    double * mxFieldRealData = static_cast<double*>(mxGetData( mxFieldData ));
    double * mxFieldImagData = static_cast<double*>(mxGetImagData( mxFieldData ));
    for(int i = 0; i < size; i++) {
        const qgcomplex & val = data[i];
        mxFieldRealData[i] = val.real();
        mxFieldImagData[i] = val.imag();
    }
    return mxFieldData;
}

void mxMarshalOutData( const mxArray * src, qgcomplex * & dest, int & size ){

    mwSize ndims = mxGetNumberOfDimensions(src);
    rassert( ndims == 2 );
    const mwSize * dims = mxGetDimensions( src );
    rassert( dims[1] == 1 );
    size = static_cast<int>( dims[0] ); 
    dest = new qgcomplex[ size ];
    const double * mxFieldRealData = static_cast<const double*>(mxGetData( src ));
    const double * mxFieldImagData = static_cast<const double*>(mxGetImagData( src ));
    for(int i = 0; i < size; i++) {
        qgcomplex & val = dest[i];
        val.real() = mxFieldRealData[i];
        val.imag() = mxFieldImagData[i];
    }
}


mxArray * mxMarshalInData( const int size, const REAL * data ){
    mwSize ndims = 1;
    mwSize dims[1] = { size };
    mxArray * mxFieldData = mxCreateNumericArray( ndims, dims, mxDOUBLE_CLASS, mxREAL );
    double * mxFieldRealData = static_cast<double*>(mxGetData( mxFieldData ));
    for(int i = 0; i < size; i++) {
        const REAL & val = data[i];
        mxFieldRealData[i] = val;
    }
    return mxFieldData;
}

void mxMarshalOutData( const mxArray * src, REAL * & dest, int & size ){
    mwSize ndims = mxGetNumberOfDimensions(src);
    rassert( ndims == 2 );
    const mwSize * dims = mxGetDimensions( src );
    rassert( dims[1] == 1 );
    size = static_cast<int>( dims[0] ); 
    dest = new REAL[ size ];
    const double * mxFieldRealData = static_cast<const double*>(mxGetData( src ));
    for(int i = 0; i < size; i++) {
        dest[i] = mxFieldRealData[i];
    }
}


template <class T, int S>
bool mxStruct::unpack( map_container<T,S> & map, string & description ) const {
    const mxContainer & descField = getField("Description");
    descField.unpack( description ); 


    // Marshall out data values
    const mxArray * mxFieldData = mxGetRawField( "Data" );
    int size;
    T * data;
    mxMarshalOutData( mxFieldData, data, size );

    const mxContainer & mxIndexList = getField("Index");
    rank<INDEX, S> * indexMap = NULL;
    mxIndexList.unpack( indexMap );

    map.store_coefficients( data, indexMap, size );
    return true;
}

template bool mxStruct::unpack( map_container<qgcomplex, 11> &, string & ) const;
template bool mxStruct::unpack( map_container<REAL, 6> &, string & ) const;
template bool mxStruct::unpack( map_container<REAL, 3> &, string & ) const;

mxStruct & mxStruct::operator=(const mxStruct & other) {
    static_cast< mxContainer & >(*this) = static_cast<const mxContainer & >(other);
}

bool mxStruct::operator==(const mxStruct & other) {
    return static_cast< mxContainer & >(*this) == static_cast<const mxContainer & >(other);
}

bool mxStruct::doesFieldExist( const char * fieldName ) const {
    if ( mxValue != NULL ){
        return mxGetFieldNumber( mxValue, fieldName ) >= 0;
    }else{
        return false;
    }
}

void mxStruct::setField(const char * fieldName, const mxContainer & value) {
    int fieldNumber = -1;
    if( ! doesFieldExist( fieldName ) ) {
        fieldNumber = mxAddField( mxValue, fieldName );
    }else{
        fieldNumber = mxGetFieldNumber( mxValue, fieldName );
        mexDestroyArray( mxGetFieldByNumber( mxValue, 0, fieldNumber ) ); 
    }
    mxSetFieldByNumber(mxValue, 0, fieldNumber, mxDuplicateArray(value.mxGetArray()) ); 
}

mxContainer mxStruct::getField( const char * fieldName ) const {
    rassert( doesFieldExist( fieldName ) );
    return mxContainer(mxDuplicateArray(mxGetField(mxValue, 0, fieldName )));
}

const mxArray * mxStruct::mxGetRawField( const char * fieldName ) const {
    rassert( doesFieldExist( fieldName ) );
    return mxGetField(mxValue, 0, fieldName );
}

// ========================================================================== //
// Class mxCellArray							      //
// ========================================================================== //
mxCellArray::mxContainerPointer::mxContainerPointer( mxArray * mx ) :
	mxContainer(mx) {};

mxCellArray::mxContainerPointer::~mxContainerPointer() {
	mxValue = NULL; // we leak the pointer because this container doesn't own it.
}


mxCellArray::mxCellArray( const mxContainer & initializer, size_t size) { 
    mwSize msize = size;
    mwSize dims[1] = { msize };
    mxValue = mxCreateCellArray(1, dims);
    for(size_t i = 0; i < size; i++){
	mxSetCell( mxValue, i, mxDuplicateArray(initializer.mxGetArray()) );
    }	
}

mxContainer & mxCellArray::operator[](size_t i) {
    rassert( mxValue != NULL );
    mxArray * cell = mxGetCell( mxValue, i );
    rassert( cell != NULL );
    mxContainerPointer * ptr = new mxContainerPointer( cell );
    refs.push_back(ptr);
    return *ptr;
}

// ========================================================================== //
// Class mxCellArrayOfStructs		            			      //
// ========================================================================== //

mxCellArrayOfStructs::mxStructPointer::mxStructPointer( mxArray * mx ) :
	mxStruct(mx) {};

mxCellArrayOfStructs::mxStructPointer::~mxStructPointer() {
	mxValue = NULL; // we leak the pointer because this container doesn't own it.
}

mxCellArrayOfStructs::mxCellArrayOfStructs( size_t size ) {
    mxStruct initializer;
    mwSize msize = size;
    mwSize dims[1] = { msize };
    mxValue = mxCreateCellArray(1, dims);
    for(size_t i = 0; i < size; i++){
	mxSetCell( mxValue, i, mxDuplicateArray(initializer.mxGetArray()) );
    }	
}

mxStruct & mxCellArrayOfStructs::operator[](size_t i) {
    rassert( this->mxValue != NULL );
    mxArray * cell = mxGetCell( this->mxValue, i );
    rassert( cell != NULL );
    if( refs.find( i ) == refs.end() ){
	refs[i] = new mxStructPointer(cell);
    }
    return * ( refs[i] );
}

void mxCellArrayOfStructs::resetRefMap() {
  for(map<size_t, mxStructPointer*>::iterator it = refs.begin(); it != refs.end(); ++it){
	delete (it->second);
  }
  refs.clear();
}


mxCellArrayOfStructs::~mxCellArrayOfStructs() {
  resetRefMap(); /* clean up the refs */
}

// ========================================================================== //
// Class MATContainer
// ========================================================================== //

MATContainer::MATContainer() : mfile(NULL) {};

MATContainer::~MATContainer() {
    if( this->mfile != NULL ) matClose(mfile);
}

bool MATContainer::isValid() const {
    return mfile != NULL;
}

// ========================================================================== //
// Class MATReader
// ========================================================================== //

MATReader::MATReader(const char * fileName) {
    mfile = matOpen( fileName, "r" );
}

mxContainer MATReader::mxGetVariable(const char * variableName) const {
    rassert( isValid() );
    mxArray * mxvar = matGetVariable( mfile, variableName);
    if( mxvar ){
        return mxContainer(mxvar);
    }else{
        return mxContainer();
    }
}

mxStruct MATReader::mxGetStruct(const char * variableName) const {
    rassert( isValid() );
    mxArray * mxstrct = matGetVariable( mfile, variableName);
    if( mxstrct && mxIsStruct( mxstrct ) ){
        return mxStruct(mxstrct);
    }else{
        return mxStruct();
    }
}

// ========================================================================== //
// Class MATWriter
// ========================================================================== //

MATWriter::MATWriter(const char * fileName) {
    mfile = matOpen( fileName, "w" );
}

void MATWriter::mxWriteVariable(const mxContainer & var, const char * varName) {
    const mxArray * data = var.mxGetArray();
    rassert( isValid() );
    matPutVariable(mfile, varName, data);
}

